// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "CheckFieldsVisitor.h"

#include <cassert>

#include "Config.h"
#include "RecordInfo.h"
#include "llvm/Support/ErrorHandling.h"

CheckFieldsVisitor::CheckFieldsVisitor(const BlinkGCPluginOptions& options)
    : options_(options), current_(0), stack_allocated_host_(false) {}

CheckFieldsVisitor::Errors& CheckFieldsVisitor::invalid_fields() {
  return invalid_fields_;
}

bool CheckFieldsVisitor::ContainsInvalidFields(RecordInfo* info) {
  stack_allocated_host_ = info->IsStackAllocated();
  managed_host_ =
      stack_allocated_host_ || info->IsGCAllocated() || info->IsNewDisallowed();
  for (RecordInfo::Fields::iterator it = info->GetFields().begin();
       it != info->GetFields().end();
       ++it) {
    context().clear();
    current_ = &it->second;
    current_->edge()->Accept(this);
  }
  return !invalid_fields_.empty();
}

void CheckFieldsVisitor::AtMember(Member*) {
  if (managed_host_)
    return;
  // A member is allowed to appear in the context of a root.
  for (Context::iterator it = context().begin();
       it != context().end();
       ++it) {
    if ((*it)->Kind() == Edge::kRoot)
      return;
  }
  bool is_ptr = Parent() && (Parent()->IsRawPtr() || Parent()->IsRefPtr());
  invalid_fields_.push_back(std::make_pair(
      current_, is_ptr ? kPtrToMemberInUnmanaged : kMemberInUnmanaged));
}

void CheckFieldsVisitor::AtWeakMember(WeakMember*) {
  AtMember(nullptr);
}

void CheckFieldsVisitor::AtIterator(Iterator* edge) {
  if (!managed_host_)
    return;

  if (!stack_allocated_host_ && edge->on_heap())
    invalid_fields_.push_back(std::make_pair(current_, kIteratorToGCManaged));
}

namespace {

CheckFieldsVisitor::Error InvalidSmartPtr(Edge* ptr, bool is_gced) {
  if (ptr->IsRefPtr()) {
    return is_gced ? CheckFieldsVisitor::Error::kRefPtrToGCManaged
                   : CheckFieldsVisitor::Error::kRefPtrToTraceable;
  }
  if (ptr->IsUniquePtr()) {
    return is_gced ? CheckFieldsVisitor::Error::kUniquePtrToGCManaged
                   : CheckFieldsVisitor::Error::kUniquePtrToTraceable;
  }
  llvm_unreachable("Unknown smart pointer kind");
}

}  // namespace

void CheckFieldsVisitor::AtValue(Value* edge) {
  RecordInfo* record = edge->value();

  // TODO: what should we do to check unions?
  if (record->record()->isUnion()) {
    return;
  }

  // Don't allow unmanaged classes to contain traceable part-objects.
  const bool child_is_part_object = record->IsNewDisallowed() && !Parent();
  if (!managed_host_ && child_is_part_object && record->RequiresTraceMethod()) {
    invalid_fields_.push_back(
        std::make_pair(current_, kTraceablePartObjectInUnmanaged));
    return;
  }

  if (!stack_allocated_host_ && record->IsStackAllocated() &&
      !Config::IsStackAllocatedIgnoreAnnotated(current_->field())) {
    invalid_fields_.push_back(std::make_pair(current_, kPtrFromHeapToStack));
    return;
  }

  if (!Parent() && record->IsGCDerived() && !record->IsGCMixin()) {
    invalid_fields_.push_back(std::make_pair(current_, kGCDerivedPartObject));
    return;
  }

  // Members/WeakMembers are prohibited if the host is stack allocated, but
  // heap collections with Members are okay.
  if (stack_allocated_host_ && Parent() &&
      (Parent()->IsMember() || Parent()->IsWeakMember())) {
    if (!GrandParent() ||
        (!GrandParent()->IsCollection() && !GrandParent()->IsRawPtr() &&
         !GrandParent()->IsRefPtr())) {
      invalid_fields_.push_back(
          std::make_pair(current_, kMemberInStackAllocated));
      return;
    }
  }

  // If in a stack allocated context, be fairly insistent that T in Member<T>
  // is GC allocated, as stack allocated objects do not have a trace()
  // that separately verifies the validity of Member<T>.
  //
  // Notice that an error is only reported if T's definition is in scope;
  // we do not require that it must be brought into scope as that would
  // prevent declarations of mutually dependent class types.
  //
  // (Note: Member<>'s constructor will at run-time verify that the
  // pointer it wraps is indeed heap allocated.)
  if (stack_allocated_host_ && Parent() &&
      (Parent()->IsMember() || Parent()->IsWeakMember()) &&
      edge->value()->HasDefinition() && !edge->value()->IsGCAllocated()) {
    invalid_fields_.push_back(std::make_pair(current_, kMemberToGCUnmanaged));
    return;
  }

  if (!Parent() || (!edge->value()->IsGCAllocated() &&
                    !edge->value()
                         ->NeedsTracing(Edge::NeedsTracingOption::kRecursive)
                         .IsNeeded())) {
    return;
  }

  // Disallow unique_ptr<T>, scoped_refptr<T>
  if (Parent()->IsUniquePtr() ||
      (Parent()->IsRefPtr() && (Parent()->Kind() == Edge::kStrong))) {
    invalid_fields_.push_back(std::make_pair(
        current_, InvalidSmartPtr(Parent(), edge->value()->IsGCAllocated())));
    return;
  }
  if (Parent()->IsRawPtr() && !stack_allocated_host_) {
    RawPtr* rawPtr = static_cast<RawPtr*>(Parent());
    Error error = edge->value()->IsGCAllocated()
                      ? (rawPtr->HasReferenceType() ? kReferencePtrToGCManaged
                                                    : kRawPtrToGCManaged)
                      : (rawPtr->HasReferenceType() ? kReferencePtrToTraceable
                                                    : kRawPtrToTraceable);
    invalid_fields_.push_back(std::make_pair(current_, error));
  }
}

void CheckFieldsVisitor::AtCollection(Collection* edge) {
  if (GrandParent() &&
      (GrandParent()->IsRawPtr() || GrandParent()->IsRefPtr())) {
    // Don't alert on pointers to unique_ptr. Alerting on the pointed unique_ptr
    // should suffice.
    return;
  }
  if (edge->on_heap() && Parent() && Parent()->IsUniquePtr())
    invalid_fields_.push_back(std::make_pair(current_, kUniquePtrToGCManaged));
}
